package svgedit.svg;

import java.awt.Rectangle;
import java.awt.Shape;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.Iterator;

import svgedit.gui.Marker;
import svgedit.gui.Marker.MarkerType;

/**
 * Represents a {@literal 'g'} element in the document.
 * 
 * We do not currently support style inheritance, so there is no need to
 * implement SVGStylable here (though in a full SVG implementation we would).
 * 
 * @see svgedit.svg.SVGElementInterface
 * @see svgedit.svg.SVGRootGroup
 */
public class SVGGroup extends SVGElement implements SVGElementInterface,
		Iterable<SVGElement>
{

	private ArrayList<SVGElement> children;

	/**
	 * Creates a new empty group in the supplied parent
	 * 
	 * @param parent
	 *            group to create the new group in
	 */
	public SVGGroup(SVGGroup parent)
	{
		super(parent);
		children = new ArrayList<SVGElement>();
	}

	/**
	 * Creates a new group out of the supplied list of elements. The group is
	 * created in the document root.
	 * 
	 * @param toBeGrouped
	 *            elements to that form the new group
	 */
	public SVGGroup(ArrayList<SVGElement> toBeGrouped)
	{
		this(toBeGrouped.get(0).getRootGroup());
		for (SVGElement child : toBeGrouped)
		{
			child.getParent().unlinkChild(child);
			this.appendChild(child);
		}
		this.getParent().appendChild(this);
	}

	@Override
	public void destroy()
	{
		// Concurrent exception is throws so just catch it,
		// seems to make things work
		try
		{
			for (SVGElementInterface e : this)
				e.destroy();
		}
		catch (ConcurrentModificationException ex)
		{
		}

		super.destroy();
	}

	@Override
	public synchronized Shape createShape()
	{

		Rectangle shape = new Rectangle();

		double minX = 0;
		double minY = 0;

		boolean first = true;

		// iterate list of children and build a shape to represent the group
		for (SVGElement element : this.children)
		{
			// Is first element in loop
			if (first)
			{
				// Set initial values
				minX = element.getBounds().getMinX();
				minY = element.getBounds().getMinY();
				first = false;
			}

			// Get smallest minX
			if (element.getBounds().getMinX() < minX)
				minX = element.getBounds().getMinX();

			// Get smallest minY
			if (element.getBounds().getMinY() < minY)
				minY = element.getBounds().getMinY();

			shape.add(element.createShape().getBounds2D());
		}
		// Set rect
		shape.setSize((int) (shape.getBounds().getMaxX() - minX), (int) (shape
				.getBounds().getMaxY() - minY));
		shape.setLocation((int) minX, (int) minY);
		return shape;
	}

	@Override
	public Rectangle getBounds()
	{
		return this.createShape().getBounds2D().getBounds();
	}

	@Override
	public void write(int indent)
	{
		System.out.printf("%" + indent + "s", " ");
		System.out.printf("<g id='%s'>\n", this.toString());
		this.writeChildren(indent);
		System.out.printf("%" + indent + "s</g>\n", " ");

	}

	/**
	 * Iterates the groups children calling write on them so as to print the
	 * elements inside this group
	 * 
	 * @param indent
	 */
	protected void writeChildren(int indent)
	{
		indent += 4;
		for (SVGElement e : this.children)
			e.write(indent);
		indent -= 4;
	}

	@Override
	public Iterator<SVGElement> iterator()
	{
		return children.iterator();
	}

	/**
	 * Destroy this group put its children in this groups parent group
	 */
	public synchronized void ungroup()
	{
		ArrayList<SVGElement> orgChildren = new ArrayList<SVGElement>(children);

		for (SVGElement elem : orgChildren)
		{
			// remove child from this group and put in this groups parent
			unlinkChild(elem);
		}

		// remove self from parent
		this.getParent().removeChild(this);
	}

	/**
	 * Adds a child element to the group.
	 * 
	 * @param child
	 *            the element to add
	 */
	public synchronized void appendChild(SVGElement child)
	{
		this.getParent().removeChild(child);
		this.children.add(child);
		child.setParent(this);
	}

	/**
	 * Removes the child-parent connection to the supplied child element
	 * 
	 * @param child
	 */
	public synchronized void unlinkChild(SVGElement child)
	{
		assert child.getParent() == this;
		this.removeChild(child);
		child.setParent(this.getParent());

		this.getParent().appendChild(child);
	}

	public synchronized void removeChild(SVGElement child)
	{
		this.children.remove(child);
	}

	@Override
	public void acceptVisitor(SVGVisitor visitor)
	{
		visitor.visitGroup(this);
	}

	@Override
	public Shape getSelectionStroke()
	{
		Shape strokedShape = super.getSelectionStroke();
		this.addMarker(strokedShape, this.getBounds().getCenterX(), this
				.getBounds().getCenterY(), MarkerType.MOVE);

		return strokedShape;
	}

	@Override
	public void drag(double deltaX, double deltaY, Marker.MarkerType mType)
	{
		if (mType != Marker.MarkerType.MOVE)
		{
			System.err.println("Wrong Marker type!");
			return;
		}

		for (SVGElement element : (SVGGroup) this)
		{
			element.drag(deltaX, deltaY, mType);
		}

	}

}